/****************************************************************************
 **
 ** This file is part of the yFiles extension package GraphML-3.2-yFiles-2.7.
 ** 
 ** yWorks proprietary/confidential. Use is subject to license terms.
 **
 ** Redistribution of this file or of an unauthorized byte-code version
 ** of this file is strictly forbidden.
 **
 ** Copyright (c) 2000-2009 by yWorks GmbH, Vor dem Kreuzberg 28, 
 ** 72070 Tuebingen, Germany. All rights reserved.
 **
 ***************************************************************************/

package demo.yext.graphml;

import org.graphdrawing.graphml.attr.AttributeConstants;
import org.graphdrawing.graphml.reader.dom.DOMGraphMLParser;
import org.graphdrawing.graphml.writer.DirectGraphMLWriter;
import org.graphdrawing.graphml.writer.DomXmlWriter;
import org.graphdrawing.graphml.writer.XmlWriter;
import org.graphdrawing.graphml.GraphMLConstants;
import y.base.Edge;
import y.base.EdgeMap;
import y.base.Graph;
import y.base.Node;
import y.base.NodeCursor;
import y.base.NodeMap;
import y.base.EdgeCursor;
import y.util.D;
import yext.graphml.reader.AttrDataAcceptorInputHandler;
import yext.graphml.reader.YGraphElementFactory;
import yext.graphml.writer.AttrDataProviderOutputHandler;
import yext.graphml.writer.YGraphElementProvider;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * This demo shows how to use the yFiles GraphML extension package to read and write 
 * instances of type {@link y.base.Graph} instead of {@link y.view.Graph2D}. 
 * This enables using the GraphML file format without using classes that are only 
 * present in the yFiles Viewer Distribution. 
 * <p>
 * The demo generates a simple graph structure and writes it to a temporary .graphml 
 * file which is again read immediately after. 
 * The .graphml file features an additional GraphML attribute that holds data of 
 * simple type (int) from a node map. 
 */
public class BaseGraphSerializationDemo {
  /**
   * The name for the additional GraphML attribute. is used with the 'attr.name' 
   * XML attribute of the GraphML <key> element. 
   */
  private static final String NODE_ATTRIBUTE_NAME = "Level";
  private static final String EDGE_ATTRIBUTE_NAME = "Level";


  /** Launcher. */
  public static void main(String[] args) {
    BaseGraphSerializationDemo demo = new BaseGraphSerializationDemo();
    demo.doit();
  }

  /**
   * Generates a simple graph, saves it and immediately after opens it. The graph's
   * structure and the levels of the nodes are printed to the console both before 
   * and after the graph has been saved/loaded. 
   */
  public void doit() {
    Graph graph;
    graph = makeGraph();
    printLevels(graph);
    saveGraph(graph);

    // Instantiate a new Graph object. 
    graph = new Graph();
    graph.addDataProvider(NODE_ATTRIBUTE_NAME+"n", graph.createNodeMap());
    graph.addDataProvider(EDGE_ATTRIBUTE_NAME+"e", graph.createEdgeMap());
    loadGraph(graph);
    printLevels(graph);
  }

  /**
   * Generates a simple graph that has tree structure.
   */
  public Graph makeGraph() {
    Graph graph = new Graph();
    // Create the nodes. 
    Node[] nodes = new Node[3];
    for (int i = 0; i < nodes.length; i++) {
      nodes[i] = graph.createNode();
    }

    // Create the edges. The graph has tree structure.
    for (int i = 1; i < nodes.length; i++) {
      Edge e = graph.createEdge(nodes[0], nodes[i]);
      D.bug("creating edge (source, target) = " + e);
    }

    // Store the levels of the tree structured graph.
    NodeMap levelMap = graph.createNodeMap();
    levelMap.setInt(nodes[0], 1);
    levelMap.setInt(nodes[1], 2);
    levelMap.setInt(nodes[2], 3);

    EdgeMap em = graph.createEdgeMap();
    for (EdgeCursor edgeCursor = graph.edges(); edgeCursor.ok(); edgeCursor.next()) {
      Edge edge = edgeCursor.edge();
      em.setInt(edge, levelMap.getInt(edge.target())-levelMap.getInt(edge.source()));
    }

    // Bind the node map to the graph.
    graph.addDataProvider(NODE_ATTRIBUTE_NAME+"n", levelMap);
    graph.addDataProvider(EDGE_ATTRIBUTE_NAME+"e", em);

    return graph;
  }

  /**
   * Writes the given graph to a temporary .graphml file. 
   */
  public void saveGraph(Graph graph) {
    try {
      String fileName = (new File("tmpLevels.graphml")).getCanonicalPath();
      D.bug("Writing graph to file " + fileName);
      writeGraph(graph, (NodeMap) graph.getDataProvider(NODE_ATTRIBUTE_NAME+"n"),
          (EdgeMap) graph.getDataProvider(EDGE_ATTRIBUTE_NAME+"e"), fileName);
    }
    catch (IOException ioEx) {
      ioEx.printStackTrace();
    }
  }

  /**
   * Populates the given Graph object with data read from a temporary .graphml file. 
   */
  public void loadGraph(Graph graph) {
    try {
      String fileName = (new File("tmpLevels.graphml")).getCanonicalPath();
      D.bug("Reading graph from file " + fileName);
      readGraph(graph, (NodeMap) graph.getDataProvider(NODE_ATTRIBUTE_NAME+"n"), (EdgeMap) graph.getDataProvider(EDGE_ATTRIBUTE_NAME+"e"), fileName);
    }
    catch (IOException ioEx) {
      ioEx.printStackTrace();
    }
  }

  /** Console output. */
  public void printLevels(Graph graph) {
    D.bug("graph structure is:\n" + graph);
    D.bug("levels are:");

    NodeMap levelMap = (NodeMap) graph.getDataProvider(NODE_ATTRIBUTE_NAME+"n");
    for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
      D.bug(nc.node() + " = " + levelMap.get(nc.node()));
    }

    EdgeMap edgeMap = (EdgeMap) graph.getDataProvider(EDGE_ATTRIBUTE_NAME+"e");
    for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
      D.bug(ec.edge() + " = " + edgeMap.get(ec.edge()));
    }
  }

  /**
   * Configures the GraphML machinery to use class {@link y.base.Graph} instead 
   * of {@link y.view.Graph2D} and writes the given graph to file. 
   */
  public void writeGraph(Graph graph, NodeMap nm, EdgeMap em, String fileName)
      throws IOException {
    FileOutputStream out = null;
    try {
      out = new FileOutputStream(fileName);

      // Low-level support for GraphML attributes of simple type.
      AttrDataProviderOutputHandler nodeAttribute =
          new AttrDataProviderOutputHandler(NODE_ATTRIBUTE_NAME, nm, AttributeConstants.TYPE_INT);
      AttrDataProviderOutputHandler edgeAttribute =
          new AttrDataProviderOutputHandler(EDGE_ATTRIBUTE_NAME, em, AttributeConstants.TYPE_INT);

      // Instead of using class Graph2D, the GraphML machinery is given a Graph object.
      YGraphElementProvider gep = new YGraphElementProvider(graph);

      // Low-level support for writing GraphML file format.
      DirectGraphMLWriter writer = new DirectGraphMLWriter();
      writer.setGraphElementProvider(gep);
      // The node attribute is registered.
      writer.addNodeOutputHandler(nodeAttribute);
      writer.addEdgeOutputHandler(edgeAttribute);

      XmlWriter coreWriter = new DomXmlWriter(out);
      coreWriter.addNamespace(GraphMLConstants.GRAPHML_BASE_NS_URI, "");
      writer.write(coreWriter);

    } finally {
      if (out != null) {
        out.close();
      }
    }
  }

  /**
   * Configures the GraphML machinery to use class {@link y.base.Graph} instead
   * of {@link y.view.Graph2D} and populates the given graph with the data read 
   * from file. 
   */
  public void readGraph(Graph graph, NodeMap nm, EdgeMap em, String fileName)
      throws IOException {
    FileInputStream in = null;
    try {
      in = new FileInputStream(fileName);

      // Low-level support for GraphML attributes of simple type.
      AttrDataAcceptorInputHandler nodeAttribute =
          new AttrDataAcceptorInputHandler("Level", nm, AttributeConstants.TYPE_INT);
      AttrDataAcceptorInputHandler edgeAttribute =
          new AttrDataAcceptorInputHandler("Level", em, AttributeConstants.TYPE_INT);

      // Instead of using class Graph2D, the GraphML machinery is given a Graph object.
      YGraphElementFactory gef = new YGraphElementFactory(graph);

      // Low-level support for processing the GraphML file format's DOM structure.
      DOMGraphMLParser parser = new DOMGraphMLParser();
      // The node attribute is registered.
      parser.addDOMInputHandler(nodeAttribute);
      parser.addDOMInputHandler(edgeAttribute);
      parser.setGraphElementFactory(gef);

      parser.parse(in);

    } finally {
      if (in != null) {
        in.close();
      }
    }
  }
}
